package taller2.model.graph.algorithms;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;

import taller2.interfaces.Connection;
import taller2.interfaces.Graph;
import taller2.interfaces.InitialNodeApplicator;
import taller2.interfaces.Node;
import taller2.model.graph.algorithms.AlgorithmApplicatorImpl;
import taller2.utils.Constants;
import taller2.utils.Constants.Colores;

public class DFSApplicator extends AlgorithmApplicatorImpl implements InitialNodeApplicator{
	private static final String path = Constants.savedGraphsPath + "DFS";
	private int initialNode;
	private boolean directed;
	
	public DFSApplicator(){
		super();
		directed = false;
	}

	public void solveProblem(Graph g){

		if(noGraph(g) || !noGraphPaths() || !noPseudocodeLines())
			return;
		
		g.unselectConnections();
		g.unselectNodes();
		g.killFluxes();
		g.killWeights();
		directed = g.isDirected();
		
		// busco el nodo inicial
		ArrayList<Node> nodes = g.getNodes();

		Node start;
		Node anterior = null;

		if (initialNode != -1)
			start = nodes.get(initialNode);	        
		else
			start = g.getFirstNode();	
		
		//Salvo estado inicial
		g.save(path);

		int cont = 0;

	    Stack<Node> nodeStack = new Stack<Node>();
	    guardarPaso(g, cont, 0, null, false);
	    cont ++;

	    HashMap<String, Node> nodeHashMap = new HashMap<String, Node>();
        ArrayList<Node> visitedNodes = new ArrayList<>();

	    while (nodeHashMap.size() != g.getNodes().size()) {

	    	guardarPaso(g, cont, 1, null, false);
	    	cont ++;

	    	//Agregar el inicial a la pila
	    	nodeHashMap.put(start.getID(), start);
	    	nodeStack.push(start);

	    	guardarPaso(g, cont, 2, start, false);
	    	cont ++;

	    	while (!nodeStack.isEmpty()) {
	    		guardarPaso(g, cont, 3, null, false);
	
	    		Node actual = nodeStack.pop();

	    		guardarPaso(g, cont, 4, null,true);
	    		cont ++;

	    		nodeHashMap.put(actual.getID(), actual);
	    		visitedNodes.add(actual);

	    		if (g.isDirected()) {
	    			pintarAristaDirected(anterior, actual, g, visitedNodes);
	    			actual.select();
	    		}else{
	    			actual.changeColor(Colores.red);
	    			pintarAristaNotDirected(anterior, actual, g, visitedNodes);
	    		}

	    		anterior = actual;

	    		guardarPaso(g, cont, 5, null, false);
	    		cont ++;

	    		for (Connection connection : actual.getConnections()) {
	    			Node node = connection.getOtherNode();
	    			guardarPaso(g, cont, 6, null, false);
	    			cont ++;

	    			if ( !nodeHashMap.containsKey(node.getID())  ) {
	    				guardarPaso(g, cont, 7, null, false);
	    				cont ++;

	    				nodeStack.push(node);
						guardarPaso(g, cont, 8, node, false);
						cont ++;

	    			}
	    		}
	    	}

	    	//busco nuevo origen
	    	for(Node n: g.getNodes()){
	    		if (!nodeHashMap.containsKey(n.getID())){
	    			start = n;
	    			break;
	    		}
	    	}
	    }

	    guardarPaso(g, cont, 9, null, false);
		cont ++;

		//Listo!, se ejecuto el algoritmo -> cargo el estado inicial para empezar a mostrarlo
		g.load(path);
	}

	public void setInitialNode(int selectedNode) {
		initialNode = selectedNode;
	}

    private void pintarAristaNotDirected(Node padre, Node hijo, Graph g, ArrayList<Node> visitedNodes) {
        boolean entro = false;
        if (padre != null) {
            for (int i  = visitedNodes.size() - 1; i >= 0; i--) {
                Node node = visitedNodes.get(i);
                    for (Connection connection : hijo.getConnections()) {
                        if (connection.getOtherNode().equals(node) && !node.equals(hijo) && !entro) {
                            node.selectConnectionsWith(hijo);
                            entro = true;
                        }
                    }
            }
        }
    }

    private void pintarAristaDirected(Node padre, Node hijo, Graph g, ArrayList<Node> visitedNodes) {
        boolean entro = false;
        if (padre != null) {
            for (int i  = visitedNodes.size() - 1; i >= 0; i--) {
                Node node = visitedNodes.get(i);
                   for (Connection connection : node.getConnections()) {
			Node other = connection.getOtherNode(); 
                        if (other.equals(hijo) && !node.equals(hijo) && !entro) {
                            if (!other.isSelected())
	                            connection.select();
                            entro = true;
                        }
                    }
		}
        }
    }
	
	private void guardarPaso(Graph g, int cont, int pcLine, Node SelNode, boolean remover){
		saveGraph(g, path + cont);
		pseudocodeLines.add(pcLine);
		String aGuardar;
		if ( (remover) && (!auxText.isEmpty()) ){
			String pasoAnterior = auxText.get(auxText.size() - 1);
			int posPrimerSeparador = pasoAnterior.indexOf(" ");
			
			if (posPrimerSeparador >= 0)
				aGuardar = pasoAnterior.substring(posPrimerSeparador + 2);
			else
				aGuardar = "";
			
			auxText.add(aGuardar);
			
			return;
		}
		
		if ( auxText.isEmpty() ){
			if (SelNode == null){
				auxText.add("");
			}
			else{
				String tag = SelNode.getTag();
				if (tag == null) { tag = Constants.sinTag;}
				auxText.add(tag + " " + "\n");
			}
		}else{
			if ((SelNode == null)){
				//si no tengo nada que agregar repito lo ultimo que halla puesto
				auxText.add(auxText.get(auxText.size() - 1));
			}
			else{
				auxText.add(SelNode.getTag() + " " + "\n" + auxText.get(auxText.size() - 1));
			}	
		}
		
	}
	
    public String getAuxText(){
    	if(auxText.isEmpty())
    		return "";
    	String ret = "Stack: \n";
    	return ret + auxText.get(position);
    }
    
	public void save(String path){
	};

	public void load(String path){
	}

	@Override
	public int getHighlightedWordNumber() {
		return -1;
	}

	public boolean wasDirected() {
		return directed;
	}

	public String getStartingGraphPath() {
		if(graphPaths == null || graphPaths.isEmpty())
			return "";
		return graphPaths.get(0);
	}
}
